Imports System.Collections.Generic
Imports System.Text
Imports System.Windows.Forms
Imports System.Globalization
Imports System.ComponentModel
Imports System.Data
Imports System

Namespace DgvFilterPopup

    ''' <summary>
    ''' Specifies how the <i>column filter</i> control is horizontally aligned inside the <i>filter host</i>.
    ''' </summary>
    Public Enum HFilterAlignment
        Top
        Bottom
        Middle
    End Enum


    ''' <summary>
    ''' Specifies how the <i>column filter</i> control is vertically aligned inside the <i>filter host</i>.
    ''' </summary>
    Public Enum VFilterAlignment
        Left
        Right
        Center
    End Enum


    ''' <summary>
    ''' The base class from which to derive effective <i>column filter</i> classes
    ''' </summary>
    ''' <remarks>
    ''' The purpose of a <i>column filter</i> control is to contain visual elements allowing the end user to construct a filter.
    ''' When inheriting from it, you can work just like creating any other user control. 
    ''' This class is a derivation of <b>UserControl</b> and provide functionalities to 
    ''' cooperate with DgvFilterManager. 
    ''' <para>
    ''' NOTE: 
    ''' This class must be intended as an abstract class. However, declaring it as abstract,
    ''' would generate errors whitin the designer when designing derived classes.
    ''' </para>
    ''' <para>
    ''' You should override <see cref="DgvBaseColumnFilter.OnFilterExpressionBuilding"/> to provide a filter expression construction 
    ''' logic and to set the values of the <see cref="DgvBaseColumnFilter.FilterExpression"/> and <see cref="DgvBaseColumnFilter.FilterCaption"/> properties. 
    ''' </para>
    ''' </remarks>      
    Public Class DgvBaseColumnFilter
        Inherits UserControl

#Region "EVENTS"

        ''' <summary>
        ''' Occurs before the filter expression is about to be built.
        ''' </summary>
        Public Event FilterExpressionBuilding As CancelEventHandler


        ''' <summary>
        ''' Occurs when the filter column is about to be initialized.
        ''' </summary>
        Public Event FilterInitializing As CancelEventHandler

#End Region


#Region "PRIVATE FIELDS"

        Private mVFilterAlignment As VFilterAlignment = VFilterAlignment.Center
        Private mHFilterAlignment As HFilterAlignment = HFilterAlignment.Middle
        Private mFilterHost As DgvBaseFilterHost
        Private mFilterManager As DgvFilterManager
        Private mDataGridViewColumn As DataGridViewColumn
        Private mBoundDataView As DataView
        Private mColumnDataType As Type
        Private mOriginalDataGridViewColumnHeaderText As String
        Private mActive As Boolean
        Private mFilterApplySoon As Boolean = True

        Private mFilterExpression As String = ""
        Private mFilterCaption As String = ""

#End Region


#Region "PROPERTIES"


        ''' <summary>
        ''' Gets or sets a value indicating whether filter apply soon after a user performs some changes.
        ''' </summary>
        ''' <value><c>true</c> (default) if to apply soon; otherwise, <c>false</c>.</value>
        Public Property FilterApplySoon() As Boolean
            Get
                Return mFilterApplySoon
            End Get
            Set(value As Boolean)
                mFilterApplySoon = value
            End Set
        End Property

        ''' <summary>
        ''' Gets and sets the filter expression.
        ''' </summary>
        ''' <remarks>
        ''' It's the filter expression on the column. Its value is used by the <see cref="DgvFilterManager"/> to build the whole filter expression.
        ''' In inherited class, set its value in the override of <see cref="DgvBaseColumnFilter.OnFilterExpressionBuilding"/>.
        ''' The filter expression must follow the rules of the DataView <see cref="System.Data.DataView.RowFilter"/> property.
        ''' </remarks>
        Public Property FilterExpression() As String
            Get
                Return mFilterExpression
            End Get
            Set(value As String)
                mFilterExpression = value
            End Set
        End Property

        ''' <summary>
        ''' Gets and sets the caption to show in the column header when the filter is active.
        ''' </summary>
        ''' <remarks>
        ''' Represents the caption to show in the column header when the filter is active.
        ''' In inherited class, set its value in the override of <see cref="DgvBaseColumnFilter.OnFilterExpressionBuilding"/>.
        ''' </remarks>
        Public Property FilterCaption() As String
            Get
                Return (If((mActive AndAlso mFilterExpression <> ""), mFilterCaption, mOriginalDataGridViewColumnHeaderText))
            End Get
            Set(value As String)
                mFilterCaption = value
            End Set
        End Property


        ''' <summary>
        ''' Gets or sets a value indicating whether the filter is active.
        ''' </summary>
        ''' <value><c>true</c> if active; otherwise, <c>false</c>.</value>
        Public Property Active() As Boolean
            Get
                Return (mActive)
            End Get
            Set(value As Boolean)
                mActive = value
            End Set
        End Property

        ''' <summary>
        ''' Specifies how the <i>column filter</i> control is horizontally aligned inside the <i>filter host</i>.
        ''' </summary>
        Public Property HFilterAlignment() As HFilterAlignment
            Get
                Return mHFilterAlignment
            End Get
            Set(value As HFilterAlignment)
                mHFilterAlignment = value
            End Set
        End Property


        ''' <summary>
        ''' Specifies how the <i>column filter</i> control is vertically aligned inside the <i>filter host</i>.
        ''' </summary>
        Public Property VFilterAlignment() As VFilterAlignment
            Get
                Return mVFilterAlignment
            End Get
            Set(value As VFilterAlignment)
                mVFilterAlignment = value
            End Set
        End Property



        ''' <summary>
        ''' Gets the <b>DataView</b> acting as the data source of the <b>DataGridView</b> to which this <i>column filter</i> is applied.
        ''' </summary>
        Public ReadOnly Property BoundDataView() As DataView
            Get
                Return mBoundDataView
            End Get
        End Property


        ''' <summary>
        ''' Gets the <i>filter host</i> control in which this <i>column filter</i> is shown.
        ''' </summary>
        Public ReadOnly Property FilterHost() As DgvBaseFilterHost
            Get
                Return mFilterHost
            End Get
        End Property


        ''' <summary>
        ''' Gets the <i>filter manager</i>.
        ''' </summary>
        Public ReadOnly Property FilterManager() As DgvFilterManager
            Get
                Return mFilterManager
            End Get
        End Property


        ''' <summary>
        ''' Gets the <b>DataGridView</b> column to which this <i>column filter</i> is applied.
        ''' </summary>
        ''' <value>The data grid view column.</value>
        Public ReadOnly Property DataGridViewColumn() As DataGridViewColumn
            Get
                Return mDataGridViewColumn
            End Get
        End Property


        ''' <summary>
        ''' Gets the type of the data bound to the <b>DataGridView</b> column.
        ''' </summary>
        Public ReadOnly Property ColumnDataType() As Type
            Get
                Return mColumnDataType
            End Get
        End Property



        ''' <summary>
        ''' Gets the original <b>DataGridView</b> column header text.
        ''' </summary>
        Public ReadOnly Property OriginalDataGridViewColumnHeaderText() As String
            Get
                Return mOriginalDataGridViewColumnHeaderText
            End Get
        End Property


#End Region


#Region "FILTER INITIALIZATION, EXPRESSION BUILDING, EVENT MANAGING"

        ''' <summary>
        ''' Called by the <i>filter manager</i>, inits the <i>column filter</i> and raises the FilterInitializing event.
        ''' </summary>
        ''' <param name="FilterManager">The <i>filter manager</i>.</param>
        ''' <param name="FilterHost">The filter host.</param>
        ''' <param name="gridColumn">The DataGridView column.</param>
        ''' <param name="boundDataView">The bound data view.</param>
        Public Sub Init(FilterManager As DgvFilterManager, FilterHost As DgvBaseFilterHost, gridColumn As DataGridViewColumn, boundDataView As DataView)
            Me.mFilterManager = FilterManager
            Me.mFilterHost = FilterHost
            Me.mDataGridViewColumn = gridColumn
            Me.mBoundDataView = boundDataView
            Me.mOriginalDataGridViewColumnHeaderText = gridColumn.HeaderText
            If gridColumn.DataPropertyName <> "" Then
                Me.mColumnDataType = boundDataView.Table.Columns(gridColumn.DataPropertyName).DataType
            Else
                Me.mColumnDataType = GetType(String)
            End If
            FilterHost.FilterClientArea.Controls.Add(Me)
            FilterHost.Location = New System.Drawing.Point(0, 0)
            Me.Visible = False
            Dim e As New CancelEventArgs(False)
            OnFilterInitializing(Me, e)
        End Sub

        ''' <summary>
        ''' Raises the <see cref="DgvBaseColumnFilter.FilterInitializing"/> event
        ''' </summary>
        ''' <param name="sender">The source of the event.</param>
        ''' <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        ''' <remarks>
        ''' When this <i>column filter</i> control is added to the <i>column filters</i> array of the <i>filter manager</i>, 
        ''' the latter calls the <see cref="DgvBaseColumnFilter.Init"/> method which, in turn, calls this method. 
        ''' You can ovverride this method to provide initialization code. 
        ''' </remarks>
        Protected Overridable Sub OnFilterInitializing(sender As Object, e As CancelEventArgs)
            ' Ovverride to add custom init code
            RaiseEvent FilterInitializing(sender, e)
        End Sub

        ''' <summary>
        ''' Forces the rebuilt of filter expression
        ''' </summary>
        ''' <remarks>
        ''' This method is called by <see cref="DgvFilterManager"/> when popup is closed, to 
        ''' force recreation of the filter expression. 
        ''' </remarks>
        Public Sub FilterExpressionBuild()
            Dim e As New CancelEventArgs(False)
            OnFilterExpressionBuilding(Me, e)
        End Sub

        ''' <summary>
        ''' Raises the <see cref="DgvBaseColumnFilter.FilterExpressionBuilding"/> event
        ''' </summary>
        ''' <param name="sender">The event source.</param>
        ''' <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        ''' <remarks>
        ''' Override <b>OnFilterExpressionBuilding</b> to provide a filter expression construction 
        ''' logic and to set the values of the <see cref="DgvBaseColumnFilter.FilterExpression"/> and <see cref="DgvBaseColumnFilter.FilterCaption"/> properties.
        ''' The <see cref="DgvFilterManager"/> will use these properties in constructing the whole filter expression and to change the header text of the filtered column.
        ''' </remarks>
        Protected Overridable Sub OnFilterExpressionBuilding(sender As Object, e As CancelEventArgs)
            RaiseEvent FilterExpressionBuilding(sender, e)
        End Sub



#End Region


#Region "HELPERS"

        ''' <summary>
        ''' Escapes a string to be suitable for filter expression.
        ''' </summary>
        ''' <param name="s">The string to escape.</param>
        ''' <returns>The escaped string</returns>
        Public Shared Function StringEscape(s As String) As String
            Dim sarray As Char() = s.ToCharArray()
            Dim sb As New StringBuilder(s.Length * 2)
            For Each c As Char In sarray
                Select Case c
                    Case "%"c, "*"c, "["c, "]"c
                        sb.Append("[" & c & "]")
                        Exit Select
                    Case "'"c
                        sb.Append("''")
                        Exit Select
                    Case Else
                        sb.Append(c)
                        Exit Select
                End Select
            Next
            Return sb.ToString()
        End Function

        ''' <summary>
        ''' Returns the string representation of the passed value, based on target type.
        ''' </summary>
        ''' <param name="value">The value to be formatted.</param>
        ''' <param name="targetType">The target type.</param>
        ''' <returns>The string representation of the passed value</returns>
        Public Shared Function FormatValue(value As Object, targetType As Type) As String
            If targetType Is GetType(String) Then
                Return "'" & value.ToString() & "'"
            End If
            Try
                value = Convert.ChangeType(value, targetType)
            Catch
                Return ""
            End Try

            If targetType Is GetType(Boolean) Then
                Return If(CBool(value), "1", "0")
            End If
            If targetType Is GetType(DateTime) Then
                Return "'" & CType(value, DateTime).ToString("yyyy'-'MM'-'dd") & "'"
            End If
            'Numeric types
            Return DirectCast(value, IFormattable).ToString(Nothing, NumberFormatInfo.InvariantInfo)
        End Function

        ''' <summary>
        ''' Returns a null condition string to be used in filter expression.
        ''' </summary>
        ''' <param name="DataColumnName">Name of the data column.</param>
        ''' <returns>A string to be used in the filter expression representing a null condition</returns>
        Public Shared Function GetNullCondition(DataColumnName As String) As String
            Return "ISNULL(CONVERT(" & DataColumnName & ",'System.String'),'NULLVALUE') = 'NULLVALUE'"
        End Function

        ''' <summary>
        ''' Returns a not null condition string to be used in filter expression.
        ''' </summary>
        ''' <param name="DataColumnName">Name of the data column.</param>
        ''' <returns>A string to be used in the filter expression representing a not null condition</returns>
        Public Shared Function GetNotNullCondition(DataColumnName As String) As String
            Return "ISNULL(CONVERT(" & DataColumnName & ",'System.String'),'NULLVALUE') <> 'NULLVALUE'"
        End Function

#End Region

    End Class
End Namespace
